/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.taobao.datasource.resource.adapter.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * A wrapper for a statement.
 *
 * @todo remove the org.jboss.ejb.plugins.cmp.jdbc.WrappedStatement dependency
 *
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
 * @author <a href="weston.price@jboss.com">Weston Price</a>
 * @version $Revision: 57189 $
 */
public class WrappedStatement implements Statement, StatementAccess {

	private final WrappedConnection lc;
	private final Statement s;

	/** The result sets */
	private HashMap resultSets;

	/** Whether we are closed */
	private boolean closed = false;

	/** The state lock */
	private Object lock = new Object();

	public WrappedStatement(final WrappedConnection lc, Statement s) {
		this.lc = lc;
		this.s = s;
		lc.registerStatement(this);
	}

	public void close() throws SQLException {
		synchronized (lock) {
			if (closed)
				return;

			closed = true;
		}
		lc.unregisterStatement(this);
		internalClose();
	}

	public boolean execute(String sql) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.execute(sql);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.execute(sql, autoGeneratedKeys);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public boolean execute(String sql, int[] columnIndexes) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.execute(sql, columnIndexes);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public boolean execute(String sql, String[] columnNames) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.execute(sql, columnNames);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public Connection getConnection() throws SQLException {
		return lc;
	}

	public SQLWarning getWarnings() throws SQLException {
		checkState();
		try {
			return s.getWarnings();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void clearWarnings() throws SQLException {
		checkState();
		try {
			s.clearWarnings();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public ResultSet executeQuery(String sql) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			ResultSet result = s.executeQuery(sql);
			return registerResultSet(result);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int executeUpdate(String sql) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.executeUpdate(sql);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.executeUpdate(sql, autoGeneratedKeys);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.executeUpdate(sql, columnIndexes);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int executeUpdate(String sql, String[] columnNames) throws SQLException {
		checkTransaction();
		try {
			checkConfiguredQueryTimeout();
			return s.executeUpdate(sql, columnNames);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getMaxFieldSize() throws SQLException {
		checkState();
		try {
			return s.getMaxFieldSize();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setMaxFieldSize(int max) throws SQLException {
		checkState();
		try {
			s.setMaxFieldSize(max);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getMaxRows() throws SQLException {
		checkState();
		try {
			return s.getMaxRows();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setMaxRows(int max) throws SQLException {
		checkState();
		try {
			s.setMaxRows(max);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setEscapeProcessing(boolean enable) throws SQLException {
		checkState();
		try {
			s.setEscapeProcessing(enable);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getQueryTimeout() throws SQLException {
		checkState();
		try {
			return s.getQueryTimeout();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setQueryTimeout(int timeout) throws SQLException {
		checkState();
		try {
			s.setQueryTimeout(timeout);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void cancel() throws SQLException {
		checkState();
		try {
			s.cancel();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setCursorName(String name) throws SQLException {
		checkState();
		try {
			s.setCursorName(name);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public ResultSet getResultSet() throws SQLException {
		checkState();
		try {
			ResultSet result = s.getResultSet();
			if (result == null)
				return null;
			else
				return registerResultSet(result);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getUpdateCount() throws SQLException {
		checkState();
		try {
			return s.getUpdateCount();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public boolean getMoreResults() throws SQLException {
		checkState();
		try {
			return s.getMoreResults();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public boolean getMoreResults(int current) throws SQLException {
		checkState();
		try {
			return s.getMoreResults(current);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setFetchDirection(int direction) throws SQLException {
		checkState();
		try {
			s.setFetchDirection(direction);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getFetchDirection() throws SQLException {
		checkState();
		try {
			return s.getFetchDirection();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void setFetchSize(int rows) throws SQLException {
		checkState();
		try {
			s.setFetchSize(rows);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getFetchSize() throws SQLException {
		checkState();
		try {
			return s.getFetchSize();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getResultSetConcurrency() throws SQLException {
		checkState();
		try {
			return s.getResultSetConcurrency();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getResultSetType() throws SQLException {
		checkState();
		try {
			return s.getResultSetType();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void addBatch(String sql) throws SQLException {
		checkState();
		try {
			s.addBatch(sql);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public void clearBatch() throws SQLException {
		checkState();
		try {
			s.clearBatch();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int[] executeBatch() throws SQLException {
		checkState();
		try {
			checkConfiguredQueryTimeout();
			return s.executeBatch();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public ResultSet getGeneratedKeys() throws SQLException {
		checkState();
		try {
			ResultSet resultSet = s.getGeneratedKeys();
			return registerResultSet(resultSet);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public int getResultSetHoldability() throws SQLException {
		checkState();
		try {
			return s.getResultSetHoldability();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public Statement getUnderlyingStatement() throws SQLException {
		checkState();
		return s;
	}

	protected SQLException checkException(Throwable t) throws SQLException {
		throw lc.checkException(t);
	}

	protected void checkTransaction() throws SQLException {
		checkState();
		lc.checkTransaction();
	}

	protected void checkConfiguredQueryTimeout() throws SQLException {
		lc.checkConfiguredQueryTimeout(this);
	}

	protected void internalClose() throws SQLException {
		synchronized (lock) {
			closed = true;
		}
		try {
			closeResultSets();
		} finally {
			s.close();
		}
	}

	void checkState() throws SQLException {
		synchronized (lock) {
			if (closed)
				throw new SQLException("The statement is closed.");
		}
	}

	protected ResultSet registerResultSet(ResultSet resultSet) {
		if (resultSet != null)
			resultSet = new WrappedResultSet(this, resultSet);

		if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
			return resultSet;

		synchronized (this) {
			if (resultSets == null)
				resultSets = new HashMap();

			if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT)
				resultSets.put(resultSet, new Throwable("STACKTRACE"));
			else
				resultSets.put(resultSet, null);
		}
		return resultSet;
	}

	protected void unregisterResultSet(WrappedResultSet resultSet) {
		if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
			return;

		synchronized (this) {
			if (resultSets != null)
				resultSets.remove(resultSet);
		}
	}

	protected void closeResultSets() {
		if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
			return;

		synchronized (this) {
			if (resultSets == null)
				return;
			for (Iterator i = resultSets.entrySet().iterator(); i.hasNext();) {
				Map.Entry entry = (Map.Entry) i.next();
				WrappedResultSet resultSet = (WrappedResultSet) entry.getKey();
				if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT) {
					Throwable stackTrace = (Throwable) entry.getValue();
					lc.getLogger().warn("Closing a result set you left open! Please close it yourself.", stackTrace);
				}
				try {
					resultSet.internalClose();
				} catch (Throwable t) {
					lc.getLogger().warn("Error closing a result set you left open! Please close it yourself.", t);
				}
			}
			resultSets.clear();
		}
	}

	// jdk 6

	public boolean isClosed() throws SQLException {
		return closed;
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return iface.isInstance(s);
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		return (T) (iface.isInstance(s) ? s : null);
	}

	public void setPoolable(boolean poolable) throws SQLException {
		checkState();
		try {
			s.setPoolable(poolable);
		} catch (Throwable t) {
			throw checkException(t);
		}
	}

	public boolean isPoolable() throws SQLException {
		checkState();
		try {
			return s.isPoolable();
		} catch (Throwable t) {
			throw checkException(t);
		}
	}
}
